package example.ilm.gui;

import ilm.framework.assignment.model.AssignmentState;
import ilm.framework.assignment.model.DomainAction;
import ilm.framework.assignment.model.DomainObject;
import ilm.framework.domain.DomainGUI;
import ilm.framework.domain.DomainModel;
import java.util.Vector; // ArrayList
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.awt.FlowLayout;

import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;

import dominio.view.Menu;

import example.ilm.model.ActionAddSubString;
import example.ilm.model.ActionRemoveSubString;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import example.ilm.model.GeometricElement;
import example.ilm.model.Square;
import example.ilm.model.Rectangle;
import example.ilm.model.RegularPolygon;

import example.ilm.model.Workspace;
import example.ilm.model.CreateGeometricElement;
import example.ilm.model.DestroyGeometricElement;
import example.ilm.model.TranslateGeometricElement;
import example.ilm.model.ScaleShape;
import example.ilm.model.RotatePolygon;
import example.ilm.model.RotateAndScalePolygon;
import example.ilm.model.SendBackwards;
import example.ilm.model.SendToBack;
import example.ilm.model.BringForwards;
import example.ilm.model.BringToFront;

import java.util.List;
import java.util.Iterator;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Shape;
import java.awt.BasicStroke;

public class IlmDomainGUI extends DomainGUI implements MouseListener, MouseMotionListener {

    /**
     * @attribute serial version due to javax.swing specification
     * @attribute five javax.swing widgets
     */
    private static final long serialVersionUID = 1L;

    // atributos ...
    private Workspace workspace = null;
    private Canvas canvas = null;
    private boolean mouseDragged; // usado para simular um mouseDrag ergonomico





    /**
     * construtor
     */
    public IlmDomainGUI() {
        // desenhos...
        this.setDoubleBuffered(true);

        // mouse
        addMouseListener(this);
        addMouseMotionListener(this);
        mouseDragged = false;
        
        // hotkeys
        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("pressed DELETE"), "destroyGeometricElement");
        getActionMap().put("destroyGeometricElement", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
            	removeFocusedGeometricElement();
            }
        });
    }



    /**
     * acoes de dominio (ligadas aos botoes da gui)
     */
    public void spawnSquare() {
        // cria um novo quadrado
        Square obj = new Square();
        obj.setPosition(100, 100);

        // executa a acao
        CreateGeometricElement action = (CreateGeometricElement)_actionList.get("createGeometricElement");
        action.setState(getCurrentState());
        action.setWorkspace(workspace);
        action.setGeometricElement(obj);
        action.execute();
    }

    public void spawnRectangle() {
        // cria um novo retangulo
        Rectangle obj = new Rectangle();
        obj.setPosition(100, 100);

        // executa a acao
        CreateGeometricElement action = (CreateGeometricElement)_actionList.get("createGeometricElement");
        action.setState(getCurrentState());
        action.setWorkspace(workspace);
        action.setGeometricElement(obj);
        action.execute();
    }

    public void spawnRegularPolygon() {
        // pede o numero de lados
        int n;
        do {
            String ans = JOptionPane.showInputDialog("Digite o numero de lados (entre 3 e 20):");
            if(ans == null || ans.equals(""))
                return;
            n = Integer.parseInt(ans);
        } while(!(n >= 3 && n <= 20));
        spawnRegularPolygon(n);
    }

    public void spawnRegularPolygon(int n) {
        if(n >= 3 && n <= 20) {
            // cria um novo n-agono
            RegularPolygon obj = new RegularPolygon(n);
            obj.setPosition(100, 100);

            // executa a acao
            CreateGeometricElement action = (CreateGeometricElement)_actionList.get("createGeometricElement");
            action.setState(getCurrentState());
            action.setWorkspace(workspace);
            action.setGeometricElement(obj);
            action.execute();
        }
        else
            JOptionPane.showMessageDialog(null, "O numero de lados deve estar entre 3 e 20.", "Erro", JOptionPane.ERROR_MESSAGE);
    }

    public void removeFocusedGeometricElement() {
        GeometricElement obj = workspace.getFocusedGeometricElement();
        if(obj != null) {
            DestroyGeometricElement action = (DestroyGeometricElement)_actionList.get("destroyGeometricElement");
            action.setState(getCurrentState());
            action.setWorkspace(workspace);
            action.setGeometricElement(obj);
            action.execute();
        }
        else
            JOptionPane.showMessageDialog(null, "Nenhum objeto em foco!", "Erro", JOptionPane.ERROR_MESSAGE);
    }

    public void sendFocusedGeometricElementToBack() {
        SendToBack action = (SendToBack)_actionList.get("sendToBack");
        action.setState(getCurrentState());
        action.setWorkspace(workspace);
        action.setGeometricElement(workspace.getFocusedGeometricElement());
        action.execute();
    }

    public void sendFocusedGeometricElementBackwards() {
        SendBackwards action = (SendBackwards)_actionList.get("sendBackwards");
        action.setState(getCurrentState());
        action.setWorkspace(workspace);
        action.setGeometricElement(workspace.getFocusedGeometricElement());
        action.execute();
    }

    public void bringFocusedGeometricElementToFront() {
        BringToFront action = (BringToFront)_actionList.get("bringToFront");
        action.setState(getCurrentState());
        action.setWorkspace(workspace);
        action.setGeometricElement(workspace.getFocusedGeometricElement());
        action.execute();
    }

    public void bringFocusedGeometricElementForwards() {
        BringForwards action = (BringForwards)_actionList.get("bringForwards");
        action.setState(getCurrentState());
        action.setWorkspace(workspace);
        action.setGeometricElement(workspace.getFocusedGeometricElement());
        action.execute();
    }








    /**
     * rotina de desenho (vai rodar a cada frame)
     */
    public void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        Graphics2D g = (Graphics2D)graphics;
        
        if(null == workspace) {
            workspace = new Workspace();
            canvas = new Canvas();
            workspace.addObserver(canvas);
            getCurrentState().addObserver(workspace);
        }
        
        canvas.render(g);
        
        Menu.domainMenuGroup.setActiveDomainGUI(this); // hack
    }   












    /**
     * rotinas de mouse
     */
    public void mouseClicked(MouseEvent e) {
        Point2D pos = e.getPoint();
        if(e.getButton() == MouseEvent.BUTTON1 && workspace != null) {
            if(!mouseDragged) {
                if(workspace.getFocusedGeometricElement() != null) {
                    // comecar a arrastar objeto
                    GeometricElement ge = workspace.getGeometricElementAt((int)pos.getX(), (int)pos.getY());
                    if(ge == workspace.getFocusedGeometricElement()) {
                        workspace.drag((int)pos.getX(), (int)pos.getY());
                        mouseDragged = true;
                    }
                    else
                        workspace.unsetFocusedGeometricElement();
                }

                if(workspace.getFocusedGeometricElement() == null) {
                    // selecionar objeto
                    GeometricElement ge = workspace.getGeometricElementAt((int)pos.getX(), (int)pos.getY());
                    if(ge != null)
                        workspace.setFocusedGeometricElement(ge);
                }
            }
            else {
                // soltar objeto
                workspace.drop(_actionList);
                mouseDragged = false;
            }

            repaint();
        }
    }

    public void mouseEntered(MouseEvent e) {
        ;
    }

    public void mouseExited(MouseEvent e) {
        ;
    }

    public void mousePressed(MouseEvent e) {
    	;
    }

    public void mouseReleased(MouseEvent e) {
    	;
    }

    public void mouseDragged(MouseEvent e) {
    	;
    }

    public void mouseMoved(MouseEvent e) {
        Point2D pos = e.getPoint();

        if(workspace != null) {
            if(!mouseDragged) {
                if(workspace.getFocusedGeometricElement() != null) {
                    workspace.getFocusedGeometricElement().unsetHighlightedVertex();

                    // sweet vertex hover effect
                    int[] vertex = new int[1];
                    GeometricElement ge = workspace.getGeometricElementAt((int)pos.getX(), (int)pos.getY(), vertex);
                    if(ge == workspace.getFocusedGeometricElement() && vertex[0] >= 0)
                        ge.setHighlightedVertex(vertex[0]);
                }
            }
            else
                workspace.drag((int)pos.getX(), (int)pos.getY());

            repaint();
        }
    }











    /**
     * Initialization of the action list. It gets the DomainModel and sets it
     * for each possible action.
     */
    public void initDomainActionList(DomainModel model) {
        DomainAction action;
        _actionList = new HashMap(); // <String, DomainAction>

        action = new CreateGeometricElement();
        action.setDomainModel(model);
        _actionList.put("createGeometricElement", action);
        
        action = new DestroyGeometricElement();
        action.setDomainModel(model);
        _actionList.put("destroyGeometricElement", action);

        action = new TranslateGeometricElement();
        action.setDomainModel(model);
        _actionList.put("translateGeometricElement", action);

        action = new ScaleShape();
        action.setDomainModel(model);
        _actionList.put("scaleShape", action);

        action = new RotatePolygon();
        action.setDomainModel(model);
        _actionList.put("rotatePolygon", action);

        action = new RotateAndScalePolygon();
        action.setDomainModel(model);
        _actionList.put("rotateAndScalePolygon", action);

        action = new SendBackwards();
        action.setDomainModel(model);
        _actionList.put("sendBackwards", action);

        action = new SendToBack();
        action.setDomainModel(model);
        _actionList.put("sendToBack", action);

        action = new BringForwards();
        action.setDomainModel(model);
        _actionList.put("bringForwards", action);

        action = new BringToFront();
        action.setDomainModel(model);
        _actionList.put("bringToFront", action);
    }

    /**
     * Initialization of the features which are dependent of the assignment
     * being shown.
     */
    protected void initDomainGUI() {
        update(null, null);
    }

    /**
     * @return the list of objects of the current assignment state. As there is
     *         no "selection" of objects in this simple example of iLM, it just
     *         return all objects.
     */
    public Vector getSelectedObjects() {
        return _state.getList();
    }

    /**
     * Usually called by the assignment's state which this is an observer.
     */
    public void update(Observable o, Object arg) {
        if (o instanceof AssignmentState) {
            _state = (AssignmentState) o;
        }

        repaint();
    }
}
